using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using System.IO.Ports;
using System.Text;

#if MINI
using SecretLabs.NETMF.Hardware.NetduinoMini;
#else
using SecretLabs.NETMF.Hardware.Netduino;
#endif

namespace codebase
{
    public class Terminal
    {
        public delegate void TerminalDataDelegate(string data, Direction dir);
        public event TerminalDataDelegate TerminalData;

        internal void sendData(string data, Direction dir)
        {
            if (TerminalData != null) TerminalData(data,dir);
        }

        public delegate void TerminalCountDelegate(int count);
        public event TerminalCountDelegate TerminalByteCount;

        internal void sendByteCount(int count)
        {
            if (TerminalByteCount != null) TerminalByteCount(count);
        }

        SerialMonitor mon;
        SerialPort sr;
        byte[] buffer = new byte[(int)FOURD.BUFFERSIZE];
        int readBufferLength = 0;
        public int maxReadBufferLength = (int)FOURD.BUFFERSIZE / 4;

        public int MaxResponseLength = 10;

        public enum Direction
        {
            Out = 0,
            In = 1
        }

        public enum Formats
        {
            HEX = 0,
            DEC = 1,
            ASCII = 2
        }

        public Formats answerFormat = Formats.ASCII;

        public Terminal(SerialMonitor monitor, SerialPort port)
        {
            mon = monitor;
            sr = port;
        }

        public bool IsOpen()
        {
            return sr.IsOpen;
        }

        public bool Open()
        {
            sr.Open();
            readBufferLength = 0;
            return sr.IsOpen;
        }

        public bool Close()
        {
            sr.Close();
            return sr.IsOpen;
        }

        public int BufferLength()
        {
            return readBufferLength;
        }

        public void setBaudRate(string baud)
        {
            sr.BaudRate = Convert.ToInt32(baud);
        }

        public void setParity(string parity)
        {
            switch (parity)
            {
                case "Even":
                    sr.Parity = Parity.Even;
                    break;
                case "Mark":
                    sr.Parity = Parity.Mark;
                    break;
                case "None":
                    sr.Parity = Parity.None;
                    break;
                case "Odd":
                    sr.Parity = Parity.Odd;
                    break;
                case "Space":
                    sr.Parity = Parity.Space;
                    break;
            }
        }

        public void setDataBits(string bits)
        {
            switch(bits)
            {
                case "7":
                    sr.DataBits = 7;
                    break;
                case "8":
                    sr.DataBits = 8;
                    break;
            }
        }

        public void setStopBits(string bits)
        {
            switch (bits)
            {
                case "None":
                    sr.StopBits = StopBits.None;
                    break;
                case "One":
                    sr.StopBits = StopBits.One;
                    break;
                case "OnePointFive":
                    sr.StopBits = StopBits.OnePointFive;
                    break;
                case "Two":
                    sr.StopBits = StopBits.Two;
                    break;
            }
        }

        private string replaceHEX(string data)
        {
            int index = data.IndexOf("[HEX]");

            while (index > -1)
            {
                string hex = data.Substring(index + 5, 2);

                string before = data.Substring(0, data.Length - (data.Length - index));
                string after = data.Substring(index + 7, data.Length - index - 7);

                data = before;

                int value = Convert.ToInt32(hex, 16);
                char c = (char)value;

                data += c;

                data += after;

                index = data.IndexOf("[HEX]");
            }

            return data;
        }

        private string replaceDEC(string data)
        {
            int index = data.IndexOf("[DEC]");

            while (index > -1)
            {
                string dec = data.Substring(index + 5, 3);

                string before = data.Substring(0, data.Length - (data.Length - index));
                string after = data.Substring(index + 8, data.Length - index - 8);

                data = before;

                int value = Convert.ToInt32(dec);
                char c = (char)value;

                data += c;

                data += after;

                index = data.IndexOf("[DEC]");
            }

            return data;
        }

        public void Send(string val)
        {
            val = val.Replace("[CR]", "\r");
            val = val.Replace("[LF]", "\n");

            val = replaceDEC(val);
            val = replaceHEX(val);

            if (val != "" && val != null)
            {
                byte[] bytes = new byte[val.Length];
                bytes = Encoding.UTF8.GetBytes(val);
                sr.Write(bytes, 0, bytes.Length);

                val = convertBytesToString(val);
                raiseEvents(val,Direction.Out);
            }
            return;
        }

        private int readAvailableData()
        {
            int bytesToRead = sr.BytesToRead;
            if (bytesToRead > 0)
            {
                sr.Read(buffer, readBufferLength, bytesToRead);
                readBufferLength += bytesToRead;

                sendByteCount(readBufferLength);

                lastTimeOut = DateTime.Now.Ticks / 10000;
            }

            return bytesToRead;
        }

        private int calculateReadLength(bool force)
        {
            long now = DateTime.Now.Ticks / 10000;
            int readNow = 0;

            if (force || now - lastTimeOut > mon.settings.timeOutInterval || readBufferLength >= maxReadBufferLength)
            {
                readNow = readBufferLength;
            }
            else
            {
                if (mon.settings.waitForAnswer)
                {
                    readNow = mon.settings.answerLength;
                }
            }

            return readNow;
        }

        private byte[] getBuffer(int size)
        {
            byte[] bytes = new byte[size];
            for (int m = 0; m < size; m++)
            {
                bytes[m] = buffer[m];
            }

            for (int i = size; i < readBufferLength; i++)
            {
                buffer[i - size] = buffer[i];
            }

            readBufferLength -= size;
            sendByteCount(readBufferLength);

            return bytes;
        }

        public void readSerialData()
        {
            readSerialData(false);
        }

        long lastTimeOut = 0;
        public void readSerialData(bool force)
        {
            if (sr.IsOpen)
            {
                int bytesToRead = readAvailableData();
                if (readBufferLength == 0) return;

                int readNow = calculateReadLength(force);
                if (readNow == 0) return;

                byte[] bytes = new byte[readNow];
                if (readBufferLength >= readNow)
                {
                    bytes = getBuffer(readNow);
                }
                else
                {
                    return;
                }

                string data = convertBytesToString(bytes);
                raiseEvents(data,Direction.In);
            }
        }

        private string convertBytesToString(string text)
        {
            byte[] bytes = UTF8Encoding.UTF8.GetBytes(text);
            return convertBytesToString(bytes);
        }

        private string convertBytesToString(byte[] bytes)
        {
            string result = "";
            foreach (byte b in bytes)
            {
                switch (answerFormat)
                {
                    case Formats.HEX:
                        string h = b.ToString("X2");
                        result = result + "[#" + h + "]";
                        break;
                    case Formats.DEC:
                        int i = (int)b;
                        string pi = i.ToString().PadLeft(3, '0');
                        result = result + "[" + pi + "]";
                        break;
                    case Formats.ASCII:
                        char c = (char)b;
                        result = result + c;
                        break;
                    default:
                        result = result + "ERR";
                        break;
                }
            }

            return result;
        }

        private void raiseEvents(string data,Direction dir)
        {
            if (data == "" || data == null) sendData("ERROR",dir);

            while (data != "")
            {
                int maxWidth = MaxResponseLength;

                int newLinePos = -1;
                int returnPos = -1;
                int specialCharPos = -1;

                if (answerFormat == Formats.ASCII)
                { 
                    newLinePos = data.IndexOf("\n");
                    returnPos = data.IndexOf("\r");
                }

                string tmpt = "";

                if (returnPos == -1)
                {
                    if (newLinePos >= 0) specialCharPos = newLinePos;
                }
                else if (newLinePos == -1)
                {
                    if (returnPos >= 0) specialCharPos = returnPos;
                }
                else
                {
                    if (returnPos < newLinePos) specialCharPos = returnPos; else specialCharPos = newLinePos;
                }

                if (specialCharPos >= 0)
                {
                    tmpt = data.Substring(0, specialCharPos);
                    sendData(tmpt, dir);
                    data = data.Substring(specialCharPos + 1, data.Length - (specialCharPos + 1));
                }
                else
                {
                    if (data.Length > maxWidth)
                    {
                        tmpt = data.Substring(0, maxWidth);
                        sendData(tmpt, dir);
                        data = data.Substring(maxWidth, data.Length - maxWidth);
                    }
                    else
                    {
                        sendData(data, dir);
                        data = "";
                    }
                }
            }
        }
    }
}
